#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include "GWindow.h"
#include "GSystem.h"
#include "GMessageManager.h"
#include "GApplication.h"
#include "GError.h"
#include "GCore.h"
#include "GThread.h"

namespace GEngine
{
	/********************************************************/
	/*					GWindowMessageHandler				*/
	/********************************************************/

	HCURSOR CursorArrow			= LoadCursor(NULL, IDC_ARROW);
	HCURSOR CursorBusy			= LoadCursor(NULL, IDC_WAIT);
	HCURSOR ResizeCursorNS		= LoadCursor(NULL, IDC_SIZENS);
	HCURSOR ResizeCursorWE		= LoadCursor(NULL, IDC_SIZEWE);
	HCURSOR	ResizeCursorNESW	= LoadCursor(NULL, IDC_SIZENESW);
	HCURSOR ResizeCursorNWSE	= LoadCursor(NULL, IDC_SIZENWSE);
	HBRUSH	BackGround			= CreateSolidBrush(RGB(128, 128, 128));

	static bool ExitValidation(GWindow* Window)
	{
		HWND Handle = (HWND)Window->GetHandle();
		int Result = ::MessageBox(Handle, "Do you want to close this window ?", "Gokay Engine", MB_ICONQUESTION | MB_YESNO);

		return Result == IDYES ? true : false;
	}

	static void SetWindowUserData(HWND Handle, void* DataPtr)
	{
		LONG_PTR Result = SetWindowLongPtr((HWND)Handle, GWLP_USERDATA, (LONG_PTR)DataPtr);
	}

	static void GetWindowUserData(HWND Handle, void** DataPtr)
	{
		*DataPtr = reinterpret_cast<void*>(GetWindowLongPtr((HWND)Handle, GWLP_USERDATA));
	}

	GWindowMessageHandler::GWindowMessageHandler()
	{
	}

	GWindowMessageHandler::~GWindowMessageHandler()
	{
	}

	GSSize GWindowMessageHandler::HandleMessage(const GSystemMessage* Message) OVERRIDE PLATFORM
	{
		GUtil::GError::DebugCheck(Message == NULL, "Empty message.");
		GUtil::GError::DebugCheck(Window == NULL, "Message for unknown window.");

		HWND WindowHandle = (HWND)Window->GetHandle();
		GUInt32 WindowMessage = Message->Message;
		GSize MessageWParam = Message->Parameter0;
		GSSize MessageLParam = Message->Parameter1;

		switch (WindowMessage)
		{
			// window notification
			case WM_MOVE:
			{
				GInt32 PosX = (GInt32)(short)LOWORD(MessageLParam);
				GInt32 PosY = (GInt32)(short)HIWORD(MessageLParam);

				Window->OnMove(PosX, PosY);

				break;
			}

			// window notification
			case WM_SIZE:
			{
				GInt32 Width =  (GInt32)(short)LOWORD(MessageLParam);
				GInt32 Height = (GInt32)(short)HIWORD(MessageLParam);

				Window->OnSize(Width, Height);

				switch (MessageWParam)
				{
					case SIZE_MAXIMIZED:
						Window->OnMaximize();
						break;
					case SIZE_MINIMIZED:
						Window->OnMinimize();
						break;
					case SIZE_RESTORED:
						Window->OnRestore();
						break;
				};
				break;
			}

			case WM_SIZING:
			{
				Window->OnSizing();
				break;
			}
			
			case WM_ENTERSIZEMOVE:
			{
				Window->OnSizeStart();
				break;
			}
			
			case WM_EXITSIZEMOVE:
			{
				Window->OnSizeEnd();
				break;
			}

			// window notification
			case WM_ENABLE:
			{
				if (MessageWParam)
				{
					Window->OnEnable();
				}
				else
				{
					Window->OnDisable();
				}
				break;
			}

			// window notification
			case WM_SHOWWINDOW:
			{
				if (MessageWParam)
				{
					Window->OnShow();
				}
				else
				{
					Window->OnHide();
				}
				break;
			}

			// paint/display message
			case WM_PAINT:
			{
				// Tell that we will update the client area
				ValidateRect(WindowHandle, NULL);
				break;
			}
			
			// cursor notification
			case WM_SETCURSOR:
			{
				GInt32 HitCode = (GInt32)(short)LOWORD(MessageLParam);
			
				switch (HitCode)
				{
					case HTTOP:
					case HTBOTTOM:
						SetCursor(ResizeCursorNS);
						break;
					case HTLEFT:
					case HTRIGHT:
						SetCursor(ResizeCursorWE);
						break;
					case HTTOPRIGHT:
					case HTBOTTOMLEFT:
						SetCursor(ResizeCursorNESW);
						break;
					case HTTOPLEFT:
					case HTBOTTOMRIGHT:
						SetCursor(ResizeCursorNWSE);
						break;
					default:
					case HTCLIENT:
						::SetCursor(CursorArrow);
						break;
				}
				break;
			}

			// keyboard notification
			case WM_ACTIVATE:
			{
				GSize HighOrder = HIWORD(MessageWParam);
				GSSize LowOrder = LOWORD(MessageWParam);
				switch(LowOrder)
				{
					case WA_ACTIVE:
					case WA_CLICKACTIVE:
						Window->OnFocusGain();
						break;
					case WA_INACTIVE:
						Window->OnFocusLoose();
						break;
				};

				break;
			}

			// keyboard notification
			case WM_SYSKEYDOWN:
			{
				GUInt32 HighOrder = (GUInt32)(short)HIWORD(MessageLParam);
				if (HighOrder & KF_ALTDOWN)
				{
					switch (MessageWParam)
					{
						case VK_RETURN:
							if (Window->GetFullScreen())
							{
								// Fullscreen to windowed
								if (Window->SetFullScreen(false))
								{
									Window->OnWindowed();
								}
							}
							else
							{
								// windowed to fullscreen
								if (Window->SetFullScreen(true))
								{
									Window->OnFullScreen();
								}
							}
							break;
						case VK_F4:
							if (ExitValidation(Window))
							{
								PostQuitMessage(0);
								::mGCore->Exit();
							}
							break;
					};
				}

				break;
			}
			
			// window notification
			case WM_CLOSE:
				if (ExitValidation(Window))
				{
					DestroyWindow((HWND)Window->GetHandle());
				}
				break;

			case WM_CREATE:
			{
				break;
			}

			case WM_DESTROY:
			{
				PostQuitMessage(0);
				::mGCore->Exit();
				break;
			}
		};

		return 0;
	}


	/********************************************************/
	/*						GWindow							*/
	/********************************************************/
	
	static void GetWin32Style(const GWindowStyle& Style, DWORD& OutWin32StyleExt, DWORD& OutWin32Style)
	{
		switch (Style.Type)
		{
			case G_WT_CAPTIONED:
				OutWin32Style |= WS_CAPTION | WS_SYSMENU;
				OutWin32Style |= Style.Captioned.Resizable ? WS_SIZEBOX : 0;
				OutWin32Style |= Style.Captioned.Maximizable ? WS_MAXIMIZEBOX : 0;
				OutWin32Style |= Style.Captioned.Minimizable ? WS_MINIMIZEBOX : 0;
			
				OutWin32StyleExt |= WS_EX_APPWINDOW;
				OutWin32StyleExt |= Style.Captioned.OnTop ? WS_EX_TOPMOST : 0;
				break;

			case G_WT_POPUP:
				OutWin32Style |= WS_POPUP;
				OutWin32Style |= Style.Popup.Resizable ? WS_SIZEBOX : 0;

				OutWin32StyleExt |= WS_EX_APPWINDOW;
				OutWin32StyleExt |= Style.Popup.OnTop ? WS_EX_TOPMOST : 0;
				break;

			case G_WT_CUSTOM:
				OutWin32Style = Style.Custom.Properties0;
				OutWin32StyleExt = Style.Custom.Properties1;
				break;
		};
	}


	static GWindow* GetGWindowPtrFromCreateParameters(GSSize lParam)
	{
		// Window not created yet so use lParam
		CREATESTRUCT* Create = reinterpret_cast<CREATESTRUCT*>(lParam);
		GWindow* Window = reinterpret_cast<GWindow*>(Create->lpCreateParams);

		return Window;
	}

	static GSSize _stdcall WindowProcedure(void* Handle, GUInt32 Message, GSize wParam, GSSize lParam) PLATFORM
	{
		// mGFileLogger->Log(G_LOG_TYPE_INFO, "Got message: %d", Message);

		GWindow* Window = NULL;

		// Try to get window from user data.
		// Can be null for the first couple of messages
		// Those messages will be passed to DefWindowProc()
		GetWindowUserData((HWND)Handle, (void**)&Window);

		// Form the message
		GSystemMessage DelayedMessage(Message, wParam, lParam, NULL);

		switch (Message)
		{
			case WM_CREATE:
			{
				Window = GetGWindowPtrFromCreateParameters(lParam);
				SetWindowUserData((HWND)Handle, Window);
				mGMessageManager->InstantMessage(Window, DelayedMessage);

				// Send internal higher level message 
				// that indicates the handler is enabled (can recieve msg)
				GSystemMessage EnableMsg(GMessageHandler::G_MSG_VAL_HANDLER_ENABLED, 0, 0, NULL);
				mGMessageManager->InstantMessage(Window, EnableMsg);

				return 0;
				break;
			}

			case WM_DESTROY:
			{
				SetWindowUserData((HWND)Handle, NULL);
				mGMessageManager->InstantMessage(Window, DelayedMessage);

				// Send internal higher level message 
				// that indicates the handler is disabled (can not recieve msg any more)
				GSystemMessage DisableMsg(GMessageHandler::G_MSG_VAL_HANDLER_DISABLED, 0, 0, NULL);
				mGMessageManager->InstantMessage(Window, DisableMsg);

				return 0;
				break;
			}

			case WM_CLOSE:
				// We will handle this message
				mGMessageManager->InstantMessage(Window, DelayedMessage);
				return 0;
				break;

			case WM_MOVE:
			case WM_SIZE:
			case WM_SIZING:
			case WM_EXITSIZEMOVE:
			case WM_ENTERSIZEMOVE:
				//	Can be modal so process immediately not to overflow our buffer
				mGMessageManager->InstantMessage(Window, DelayedMessage);
				return 0;
				break;

			case WM_INPUT:
			case WM_INPUT_DEVICE_CHANGE:
				// After wndproc the input handle becomes invalid
				mGMessageManager->InstantMessage(Window, DelayedMessage);
				return 0;
				break;
			
			case WM_INPUTLANGCHANGEREQUEST:
			case WM_INPUTLANGCHANGE:
				mGMessageManager->InstantMessage(Window, DelayedMessage);
				break;

			case WM_PAINT:
				// After wndproc validaterect fails.
				mGMessageManager->InstantMessage(Window, DelayedMessage);
				return 0;
				break;

			case WM_ENABLE:
			case WM_ACTIVATE:
			case WM_SETCURSOR:
			case WM_SHOWWINDOW:
			case WM_SYSKEYDOWN:
				mGMessageManager->InstantMessage(Window, DelayedMessage);
				break;

			default:
				break;
		};

		return DefWindowProc((HWND)Handle, Message, wParam, lParam);
	}
	
	static bool ConvertClientToWindowSize(GUInt32 Style, GUInt32 StyleExt, GInt32 ClientX, GInt32 ClientY, GInt32 ClientWidth, GInt32 ClientHeight, GInt32& WindowX, GInt32& WindowY, GInt32& WindowWidth, GInt32& WindowHeight)
	{
		RECT Viewport = {0};
		Viewport.left = ClientX;
		Viewport.top = ClientY;
		Viewport.right = ClientX + ClientWidth;
		Viewport.bottom = ClientY + ClientHeight;

		BOOL Result = ::AdjustWindowRectEx(&Viewport, Style, false, StyleExt);
		if (Result == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}
		
		WindowX = Viewport.left;
		WindowY = Viewport.top;
		WindowWidth = (Viewport.right -  Viewport.left);
		WindowHeight = (Viewport.bottom - Viewport.top);

		return true;
	}

	bool GWindow::StaticInitialize() PLATFORM
	{
		// Register window calss for main app instance
		HINSTANCE Instance = (HINSTANCE)GUtil::GSystem::GetInstance(NULL);

		WNDCLASSEX WindowClass;
		WindowClass.cbSize			= sizeof(WNDCLASSEX);
		WindowClass.style			= CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
		WindowClass.lpfnWndProc		= (WNDPROC)&WindowProcedure;
		WindowClass.cbClsExtra		= 0;
		WindowClass.cbWndExtra		= 0;	// No extra allocation(size)
		WindowClass.hInstance		= Instance;
		WindowClass.hIcon			= LoadIcon(NULL, IDI_EXCLAMATION);
		WindowClass.hIconSm			= LoadIcon(NULL, IDI_EXCLAMATION);
		WindowClass.hCursor			= CursorArrow;
		WindowClass.hbrBackground	= BackGround;
		WindowClass.lpszClassName	= "GEngineWindowClass";
		WindowClass.lpszMenuName	= NULL;
		
		if(RegisterClassEx(&WindowClass) == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}

		return true;
	}

	bool GWindow::StaticUninitialize() PLATFORM
	{
		HINSTANCE Instance = (HINSTANCE)GUtil::GSystem::GetInstance(NULL);

		BOOL Result = UnregisterClass("GEngineWindowClass", Instance);
		if (Result == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}

		return true;
	}

	bool GWindow::SetStyle(const GWindowStyle& WindowStyle) PLATFORM
	{
		DWORD Win32Style = 0;
		DWORD Win32StyleExt = 0;
		GetWin32Style(WindowStyle, Win32StyleExt, Win32Style);
		
		LONG_PTR Result = 0;
		Result = SetWindowLongPtr((HWND)Handle, GWL_STYLE, Win32Style);
		if (Result == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}

		Result = SetWindowLongPtr((HWND)Handle, GWL_EXSTYLE, Win32StyleExt);
		if (Result == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}

		return true;
	}

	bool GWindow::SetTitle(const char* String) PLATFORM
	{
		GUtil::GError::DebugCheck(Handle == NULL, "Window not created.");

		BOOL Result = ::SetWindowText((HWND)Handle, String);
		if (Result == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}

		Title = String;
		return true;
	}

	bool GWindow::SetPosition(GInt32 X, GInt32 Y) PLATFORM
	{
		GUtil::GError::DebugCheck(Handle == NULL, "Window not created.");

		WINDOWINFO WindowInfo;
		BOOL Result = ::GetWindowInfo((HWND)Handle, &WindowInfo);
		if (Result == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}

		GInt32 WindowPosX, WindowPosY;
		GInt32 WindowWidth, WindowHeight;
		ConvertClientToWindowSize(WindowInfo.dwStyle, WindowInfo.dwExStyle, X, Y, Width, Height, WindowPosX, WindowPosY, WindowWidth, WindowHeight);
	
		Result = ::MoveWindow((HWND)Handle, WindowPosX, WindowPosY, WindowWidth, WindowHeight, TRUE);
		if (Result == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}

		return true;
	}

	bool GWindow::SetSize(GInt32 Width, GInt32 Height) PLATFORM
	{
		GUtil::GError::DebugCheck(Handle == NULL, "Window not created.");
	
		WINDOWINFO WindowInfo;
		BOOL Result = ::GetWindowInfo((HWND)Handle, &WindowInfo);
		if (Result == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}

		GInt32 WindowPosX, WindowPosY;
		GInt32 WindowWidth, WindowHeight;
		ConvertClientToWindowSize(WindowInfo.dwStyle, WindowInfo.dwExStyle, PositionX, PositionY, Width, Height, WindowPosX, WindowPosY, WindowWidth, WindowHeight);
	
		Result = ::MoveWindow((HWND)Handle, WindowPosX, WindowPosY, WindowWidth, WindowHeight, TRUE);
		if (Result == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}
		
		return true;
	}

	// d3d,opengl inheritance will override this
	bool GWindow::SetFullScreen(bool Value) PLATFORM
	{
		return true;
	}

	// d3d,opengl inheritance will override this
	bool GWindow::SetVSynch(bool Value, GUInt32 Interval) PLATFORM
	{
		VSynchEnabed = Value;
		VSynchInterval = Interval;
		return true;
	}

	void GWindow::Enable() PLATFORM
	{
		::EnableWindow((HWND)Handle, true);
	}

	void GWindow::Disable() PLATFORM
	{
		::EnableWindow((HWND)Handle, false);
	}

	void GWindow::Show() PLATFORM
	{
		GUtil::GError::DebugCheck(Handle == NULL, "Window not created.");

		::ShowWindow((HWND)Handle, SW_SHOW);
		::UpdateWindow((HWND)Handle);
	}

	void GWindow::Hide() PLATFORM
	{
		GUtil::GError::DebugCheck(Handle == NULL, "Window not created.");

		::ShowWindow((HWND)Handle, SW_HIDE);
		::UpdateWindow((HWND)Handle);
	}

	void GWindow::Focus() PLATFORM
	{
		::SetFocus((HWND)Handle);
	}

	bool GWindow::Restore() PLATFORM
	{
		GUtil::GError::DebugCheck(Handle == NULL, "Window not created.");

		BOOL Result =  OpenIcon((HWND)Handle);
		if (Result == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}

		return true;
	}

	bool GWindow::Maximize() PLATFORM
	{
		GUtil::GError::DebugCheck(Handle == NULL, "Window not created.");
	
		BOOL Result = ::ShowWindow((HWND)Handle, SW_SHOWMAXIMIZED);
		if (Result == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}

		return true;
	}

	bool GWindow::Minimize() PLATFORM
	{
		GUtil::GError::DebugCheck(Handle == NULL, "Window not created.");

		BOOL Result =  CloseWindow((HWND)Handle);
		if (Result == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}

		return true;
	}

	bool GWindow::Create(void* Handle) PLATFORM
	{
		GUtil::GError::Error("Function not implemented.");
		//! Initialize with an handle so it can work under other apps
		//! this mode may require an empty release method
		return false;
	}

	bool GWindow::Create(GInt32 X, GInt32 Y, GInt32 W, GInt32 H, const char* Name, const GWindowStyle& WindowStyle, bool ShowImmediately) PLATFORM
	{
		GUtil::GError::DebugCheck(Handle != NULL, "Window is already created.");

		DWORD Win32Style = 0;
		DWORD Win32StyleExt = 0;

		GetWin32Style(WindowStyle, Win32StyleExt, Win32Style);
	
		GInt32 WindowPosX, WindowPosY;
		GInt32 WindowWidth, WindowHeight;
		ConvertClientToWindowSize(Win32Style, Win32StyleExt, X, Y, W, H, WindowPosX, WindowPosY, WindowWidth, WindowHeight);

		// Use main app instance for all windows
		HINSTANCE Instance = (HINSTANCE)GUtil::GSystem::GetInstance(NULL);
		HWND WindowHandle = CreateWindowEx(WS_EX_APPWINDOW, "GEngineWindowClass", Name, Win32Style, WindowPosX, WindowPosY, WindowWidth, WindowHeight, NULL, NULL, Instance, this);
		if (WindowHandle == NULL)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}

		Handle = WindowHandle;
		PositionX = X;
		PositionY = Y;
		Width = W;
		Height = H;
		Title = Name == NULL ? "" : Name;
		Style = WindowStyle;

		if (ShowImmediately)
		{
			Show();
		}


		return true;
	}

	bool GWindow::Release() PLATFORM
	{
		BOOL Result = 0;

		Result = DestroyWindow((HWND)Handle);
		if (Result == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}

		Handle = NULL;
		Title = "Unnamed_window";

		Width = 0;
		Height = 0;

		PositionX = 0;
		PositionY = 0;

		VSynchEnabed = true;
		VSynchInterval = 1;

		State = InitialState;
		Style = DefaultStyle;

		return true;
	}

}